query-stream 1.2.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +215 -0
- data/README.md +214 -3
- data/RELEASE_NOTE.md +100 -0
- data/lib/query_stream/command.rb +11 -8
- data/lib/query_stream/configuration.rb +9 -8
- data/lib/query_stream/data_resolver.rb +23 -4
- data/lib/query_stream/errors.rb +24 -3
- data/lib/query_stream/filter_engine.rb +102 -25
- data/lib/query_stream/query_stream_parser.rb +48 -35
- data/lib/query_stream/singularize.rb +0 -2
- data/lib/query_stream/template_compiler.rb +93 -17
- data/lib/query_stream/version.rb +1 -1
- data/lib/query_stream.rb +71 -23
- data/query-stream.gemspec +9 -9
- metadata +24 -23
- data/Gemfile +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0386ceba9bb9c31fb12bc7d02a7ef3ab23e1df189738e496e6bca25dd14b1cc9'
|
|
4
|
+
data.tar.gz: 1bc4884b4426a0bd2bd76ffa5c1f865742857905f7ba2c4dd2a85e57c1b5aaab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9157468004852779de4ceb88f3ee7cf248ab2c88da7a97b6a2126831f9eaf2e24016909cea8283ac11bbdcbd909da15d576fd121cfea08403f832e4d996afb48
|
|
7
|
+
data.tar.gz: 1a92018c0677b067a487d07a51c6f337daa7bdb66ec3282aa0e75fec2e2f402ba090c23d02a0ec1f5746ecd4b5fb17c74137a4114793fea626dc251dd55c6481
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Planned
|
|
10
|
+
- パフォーマンス最適化
|
|
11
|
+
- 追加のテンプレートスタイル
|
|
12
|
+
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
## [1.5.0] - 2026-11-01
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **値の無い項目の部分だけを消せるようにした** (`template_compiler.rb`): 変数が 2 つ以上ある行は、区切り(`/`・`・`・`、`・`,`・`,` と、前後に空白のある ` / `)と、変数を含む括弧(`(…)`・`(…)`)で部品に分け、値の無い変数を含む部品だけを、区切りを詰めて消す。`= author.name 著/= publisher/= year 年` は、発行年の分からない本でも `高橋征義 著/SBクリエイティブ` と出る(これまでは著者まで行ごと消えていた)。`= author.name 著(= year年)` も、発行年が無ければ `高橋征義 著` になる。部品が 1 つも残らなければ、今までどおり行ごと消す。すべての値がそろった行の出力は変わらない。
|
|
19
|
+
- 変数が 1 つだけの行と、表の行(`|` で始まる行)は今までどおり行ごと消す(セルを消すと列がずれる)。
|
|
20
|
+
- 行頭の Markdown の目印(`- `・`1. `・`> `・`# `)は、先頭の部品が消えても残す。
|
|
21
|
+
- 空白なしの半角の `/` は区切りにせず(URL を割らない)、`]` の直後の括弧(リンク先)も部品にしない。
|
|
22
|
+
- **抽出条件の数を、アンダースコアで桁区切りして書けるようにした** (`query_stream_parser.rb` / `filter_engine.rb`): `population>=9_000_000` や `population=900_0000..` のように書ける。Ruby の数値リテラルと同じく区切りは数字と数字のあいだにだけ置け、区切る位置は問わない(万の位で区切る `900_0000` も書ける)。比較(`>=` など)・範囲(`..`)・主キー検索に加え、等値(`=`)でも区切りを外して比べる。等値ではデータ側の値からも区切りを外すので、データに `"2024_01"` のような文字列があっても同じ綴りの条件で一致する。`snake_case` のような数でない値のアンダースコアはそのまま残す。
|
|
23
|
+
- **日付を和暦(`R06-01-01`)でも書けるようにした** (`filter_engine.rb`): データでも抽出条件でも、西暦の `2024-01-01` と同じ日付として比べ、並べ替える。元号は頭文字(M・T・S・H・R)で書き、年は 1 桁でもよい(`R6-01-01`)。等値(`=`)でも和暦と西暦を同じ日として扱う。変換は `Date.jisx0301` に任せた——`Date.parse` は `"R06-01-01"` を 2006-01-01 と読み違えるため使えない。元号の境目は検査しない(`H31-05-01` も 2019-05-01 として読む)。存在しない日(`R06-02-30`)は、西暦と同じく `InvalidDateError` になる。
|
|
24
|
+
|
|
25
|
+
- **抽出条件に `OR` / `or` を足した** (`query_stream_parser.rb` / `filter_engine.rb`): `tags=ruby OR year>=2020` のように、別の項目の条件もつなげる。AND は OR より強く結び付き、`a=1 AND b=2 OR c=3` は「a かつ b」または「c」になる。2 件目以降の項目名は省ける(`tags=ruby OR go`)。`||` は記法の区切りの `|` と衝突するので扱わない。前後に空白のある `OR` / `or` は区切りになるので、抽出条件の値に書けなくなった(`title=Rock or Pop`)。値だけの主キー検索(`= book | Rock or Pop`)は今までどおり 1 つの検索語として扱う。
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **項目名を省いた AND が OR として動いていた** (`query_stream_parser.rb`): `tags=ruby AND beginner` は、`ruby` と `beginner` の両方を含むレコードを選ぶ仕様だが、値を前の条件の値リストへ足していたため、どちらかを含むレコードを選んでいた。別の条件として組み立てる。誤った動きを期待していたテストも直した。
|
|
29
|
+
- **比較とソートの対象の項目に値が無いレコードや比較できない値があると、`ArgumentError` が `render` の外へ出ていた** (`filter_engine.rb`): `on_error` にも渡らず、展開全体が止まった。比較では該当のレコードを除き、ソートでは最も多い種類の値を比べ、それ以外の値と値が無いレコードを昇順でも降順でも末尾に置く。
|
|
30
|
+
- **両端を比較できない範囲で `ArgumentError` が出ていた** (`query_stream_parser.rb` / `filter_engine.rb`): `year=2020..abc` は `Range` を作れない。文字列の値として扱い、どのレコードにも一致しない。
|
|
31
|
+
- **省ける項目が最初の 1 件に無いと、打ち間違いとして止まっていた** (`template_compiler.rb`): テンプレートのキーを確かめるとき、最初の 1 件のキーしか見ていなかった。1 冊目に `year` が無いだけで `UnknownKeyError` になり、値の無い項目を消す仕組みが働く前に止まった。どれか 1 件にあるキーは正しいキーとみなす。どの件にも無いキーは、今までどおり打ち間違いとして知らせる。
|
|
32
|
+
- **日付の範囲指定が 1 件も返さなかった** (`filter_engine.rb`): `date=2024-06-01..2024-06-30` のような範囲は、両端が文字列のまま比べられ、日付に直したデータの値を含まなかった。両端も比較用にそろえてから判定する。数の範囲は両端が最初から数なので影響を受けていなかった。
|
|
33
|
+
- **昇順ソートの書き方が README に無かった** (`README.md`): 例に並ぶのは降順(`-title`)だけで、昇順の `+title` はどこにも出てこない。実装は昇順を持っている——出口が塞がっていただけである。
|
|
34
|
+
|
|
35
|
+
自然に思いつくのは符号を付けない `= books | title` のほうだが、これは**主キー検索**として解釈され、該当なしの警告とともに何も展開されない(`= book | 楽しいRuby` と綴りが同じで、符号の有無だけが見分ける手掛かりになっている)。警告は出るので黙って壊れはしないが、書いた本人は「ソートしたつもり」なので、読んで気づける場所に書いておく必要があった。例へ `+title` / `-year` を足し、符号が必須である理由も添えた。
|
|
36
|
+
|
|
37
|
+
- **一度も実行されない分岐が残っていた** (`query_stream_parser.rb`): `parse_sort` は符号を省いた `title` を昇順として扱う分岐(`/\A?(.+)\z/`)を持ち、コメントも受け付ける綴りとして `"title"` を挙げていた。しかし振り分け側(`parse_segment`)は `/\A[+-][a-zA-Z_]/` に一致した綴りしか渡さないため、**符号なしはここへ届かない**。
|
|
38
|
+
|
|
39
|
+
素の名前を昇順にすると主キー検索と区別が付かなくなるので、**いまの仕様のほうが正しい**。分岐とコメントが古かったので、符号が必須であることを前提に書き直した。挙動は変えていない(`+` / `-` の両方を実測で確認)。
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
- **対応 Ruby を 3.4.10 / 4.0.9 にした**。4.0.9 は 2026-10-23 公開。
|
|
43
|
+
- **依存を最新へ上げた**: Minitest → 6.0.6、samovar 2.4.1 → 2.5.1、rake 13.3.1 → 13.4.2。gemspec の minitest は `~> 5.22` から `>= 5.22` へ緩めた——5 系に固定する理由が無く、6 でも 158 件すべて通る(`stub` / `mock` を使っていないため、6 での `minitest/mock` 分離の影響も受けない)。
|
|
44
|
+
- **rubocop を入れた**(`.rubocop.yml`・`rake rubocop`・CI)。見るのは `Lint` と `Security` だけで、引用符や行数は見ない。他の 2 つの gem と同じ構えである。現状 0 件。
|
|
45
|
+
- **README と CHANGELOG の「vivlio-style」を「Vivlio Starter」にした**。製品名を崩した綴りだった。
|
|
46
|
+
|
|
47
|
+
## [1.4.0] - 2026-08-07
|
|
48
|
+
|
|
49
|
+
### Removed
|
|
50
|
+
- **`QueryStream.logger` と `Configuration#logger` を削除した** (`query_stream.rb`, `configuration.rb`, `query-stream.gemspec`): 下記の `logger.error` 撤去により、gem 内でロガーを読む箇所がゼロになった。設定できるのに何も起きない項目は、利用者に「ログ出力を制御できる」という誤った期待を持たせるだけなので残さない。`logger` gem への依存も外した。
|
|
51
|
+
- **破壊的変更**: `config.logger = ...` を書いているコードは削除が必要(設定しても何も起きないため、消すだけでよい)。
|
|
52
|
+
- 唯一の利用者である vivlio-starter は `QueryStream.configure` 自体を使っておらず、影響は無い。
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
- **`UnknownKeyError` を構造化例外にし、gem 内に最後まで残っていた `logger.error` を撤去した** (`errors.rb`, `template_compiler.rb`): 1.1.0 で `logger.error`、1.2.0 で `logger.warn` を廃したが、`TemplateCompiler` がテンプレートのキーを検証する 2 箇所にだけ `logger.error` が残っており、「gem 内のログ出力を全廃した」という設計が最後まで通っていなかった。`UnknownKeyError` は属性を 1 つも持たなかったため、**呼び出し元は「利用できるキーの一覧」を受け取れず**、gem が直接吐くログに頼るしかなかったのが原因である。`key_path` / `available_keys` / `template_path` / `location` を持たせ、ログ出力を削除した。これで呼び出し元は「もしかして `=title`?」のような案内を自前で組み立てられる。
|
|
56
|
+
- `location`(記法が書かれた原稿の位置)と `template_path`(打ち間違いがある雛形の位置)は別物として持つ。**直すのは雛形**なので、記法の位置だけでは著者はファイルを開けない。
|
|
57
|
+
- `TemplateCompiler.render` に `template_path:` キーワードを追加した(任意・既定 `nil`)。
|
|
58
|
+
- **必要な Ruby を 4.0 以上から 3.4 以上へ緩めた** (`query-stream.gemspec`): Ruby 4.0 固有の機能は使っていない。Prism のバージョン指定パースで全 14 ファイルを解析したところ 4.0.0 / 3.4.0 / 3.3.0 いずれも構文エラー 0 件で、実際の下限を決めていたのは `it`(暗黙ブロック引数・16 箇所)**単独**だった。`it` は 3.3 でも構文エラーにならず「`it` というメソッドの呼び出し」として通り、実行時に `NameError` になる——静的解析では見つからないので、Ruby 3.4.10 で実走して全テスト通過を確認している。コード変更はゼロ(gemspec 1 行のみ)。
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- **壊れた入力で生の例外が外へ漏れていたのを直した**: いずれも「著者が用意した原稿・データが壊れている」という正当に起こりうる状況で、`QueryStream::Error` 系ではない例外が呼び出し元へ届いていた。呼び出し元は例外型で扱いを分けるため、生の `ArgumentError` / `NoMethodError` が出ると「どのファイルのどこが悪いか」を著者へ案内できない。
|
|
62
|
+
- **空のデータファイルで `NoMethodError`** (`data_resolver.rb`): YAML が `nil` を返すのを素通ししており、`TemplateCompiler` が `undefined method 'any?' for nil` で落ちていた。`load_records` は**常に配列を返す**契約に正規化した(`@return [Array<Hash>]` という既存の記述に実装を合わせた形)。レコードの配列でもハッシュでもない中身(スカラーだけの YAML)は、何が入っていたかを添えて `DataLoadError` にする。
|
|
63
|
+
- **不正な UTF-8 を含む原稿で `ArgumentError`** (`query_stream.rb`): `String#match?` も `lstrip` も不正バイト列に対して `invalid byte sequence in UTF-8` を投げるため、原稿に 1 バイトの壊れが混ざっただけで展開が丸ごと止まっていた。読めない行は**本文としてそのまま返す**。scrub して通す設計は採らない——gem が黙ってバイト列を書き換えると、著者が気付けない形で本文が変わる。
|
|
64
|
+
- **`scan` に本文を渡すと `ArgumentError` / `Errno::EISDIR`** (`query_stream.rb`): `scan` はパスと本文の両方を受け取る API なのに、引数を無条件で `File.exist?` へ渡していた。NUL バイトを含む本文で `path name contains null byte`、ディレクトリ名で `File.exist?` が true を返し `File.read` が `Errno::EISDIR` になっていた。パスとして成立しうる形(NUL・改行を含まず `MAX_PATH_BYTES` 以下で、かつ `File.file?`)だけを読み、判定自体の失敗も本文扱いに倒す。
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
- **堅牢性テストを整備した** (`test/robustness/`): 「正しく使ったとき正しく動くか」を見る既存のユニットテストとは別に、**想定外の入力・環境に対する振る舞い**を固定する 27 件を追加。守る不変条件は「gem の外へ出てよい例外は `QueryStream::Error` の系統だけ」。上記 3 件の修正はいずれもこのテストを書く過程で見つかったもの。
|
|
68
|
+
- `data_file_safety_test.rb` — `!ruby/object` 等の危険なタグ・壊れた YAML/JSON・空ファイル・レコードでない中身
|
|
69
|
+
- `broken_encoding_test.rb` — 不正な UTF-8 を含む原稿。**バイト列を書き換えずに返す**ことまで固定
|
|
70
|
+
- `scan_input_test.rb` — `scan` のパス/本文判定(NUL・改行・超長入力・ディレクトリ)
|
|
71
|
+
- `missing_source_test.rb` — データ/テンプレート不在時の例外属性と、1 行の失敗で全体を止めないこと。**当てずっぽうの hint を返さない**ことも固定
|
|
72
|
+
- `unreadable_file_test.rb` — 権限エラーは `DataLoadError` へ包まず `Errno::EACCES` のまま伝える(環境の問題とデータの問題は直し方が違うため)
|
|
73
|
+
|
|
74
|
+
## [1.3.0] - 2026-07-12
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
- **`post_render` 後段フィルタコールバックを追加** (`lib/query_stream.rb`, `lib/query_stream/configuration.rb`): 1 記法の展開結果を、コンテキスト付きで呼び出し元へ通す汎用フックを新設。`QueryStream.render` / `render_query` に `post_render:` キーワードを追加し、`Configuration#post_render` からも指定できる。コールバックは `(text, context)` を受け取り、`context` は `source`(記法の論理名)・`data_file`(単複解決後の実パス)・`data_dir`・`template_path`・`query`・`location` を含む。戻り値が String ならそれを、String 以外(nil 含む)なら元の展開結果を採用する。gem 自身は用途を規定せず、画像パス解決などの呼び出し元固有の後処理を委譲できる。コールバック内の例外は握り潰さず伝播する(`render` の既存 rescue は `QueryStream::Error` のみ捕捉するため、それ以外の後処理例外の扱いは呼び出し元の責務)。
|
|
78
|
+
|
|
79
|
+
## [1.2.2] - 2026-04-26
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
- **アンダースコア付き複合名の単数形変換が誤る問題を修正** (`lib/query_stream/singularize.rb`): `physics_books` が `physic_books` に変換されていた問題を修正。アンダースコアで分割し、末尾から複数形セグメントを探して単数化するよう変更。`people` は不規則変化として優先処理。`physics_books` → `physics_book`、`books_nested` → `book_nested` が正しく変換されるようになった。
|
|
83
|
+
|
|
84
|
+
## [1.2.1] - 2026-04-20
|
|
85
|
+
|
|
86
|
+
### Security
|
|
87
|
+
- **`DataResolver.load_records` を `YAML.safe_load_file` に移行** (`lib/query_stream/data_resolver.rb`):
|
|
88
|
+
従来は `YAML.load_file(file_path, symbolize_names: true)` を使用しており、Psych のバージョンや将来のアップデートで `!ruby/object` などの Ruby オブジェクトタグが受理されてしまう可能性があった。
|
|
89
|
+
`permitted_classes: [Symbol, Time, Date, DateTime]` と `aliases: true` を明示的に指定する `YAML.safe_load_file` に置き換え、安全性を Psych バージョン非依存にした。
|
|
90
|
+
これにより悪意のあるデータファイル(`!ruby/object:Kernel {}` 等)を読み込ませて任意コード実行を試みる攻撃ベクトルを明示的に塞いだ。
|
|
91
|
+
なお Symbol が許可されているため `symbolize_names: true` の挙動は従来どおり維持される。
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
- **`QueryStream::DataLoadError` 新規例外クラス** (`lib/query_stream/errors.rb`):
|
|
95
|
+
`Psych::DisallowedClass` / `Psych::SyntaxError` / `JSON::ParserError` を呼び出し元に優しいメッセージ付きの `DataLoadError` に変換する。
|
|
96
|
+
`file_path` と `cause_error` 属性を持ち、呼び出し元で詳細なログ出力や i18n が可能。
|
|
97
|
+
|
|
98
|
+
### Tests
|
|
99
|
+
- **セキュリティテスト 7 件を追加** (`test/query_stream_test.rb`):
|
|
100
|
+
- `!ruby/object:Object {}` を含む YAML は `DataLoadError` で拒否
|
|
101
|
+
- `!ruby/struct:Point` / `!ruby/hash:MyCustomHash` も同様に拒否
|
|
102
|
+
- Symbol / Time / Date / DateTime は正常に読み込める
|
|
103
|
+
- YAML / JSON 構文エラーは `DataLoadError` に変換される
|
|
104
|
+
- 通常の YAML anchor / alias は正常に展開される(DoS 耐性の副次確認)
|
|
105
|
+
|
|
106
|
+
## [1.2.0] - 2026-04-19
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
- **`NoResultWarning` / `AmbiguousQueryWarning` を構造化例外に変更し、gem 内の `logger.warn` 呼び出しを全廃** (`lib/query_stream.rb`, `lib/query_stream/errors.rb`): 1.1.0 で `logger.error` は廃止されていたが、`render_query` の一件検索分岐に `logger.warn("一件検索で該当なし(…): …")` / `logger.warn("一件検索で複数件ヒット(…): …")` が残存しており、呼び出し元(vivlio-starter 等)が `⚠️` プレフィックスや i18n を付与できない問題があった。`NoResultWarning` / `AmbiguousQueryWarning` に `query` / `location` (ambiguous は `count` も)属性を追加し、`logger.warn` 呼び出しを削除。新たに `QueryStream.render` / `render_query` に `on_warning:` コールバックを追加し、警告情報を構造化例外として呼び出し元へ委譲する。これにより gem 内のログ出力が完全になくなり、メッセージ構成・ログ出力・i18n はすべて呼び出し元の責務となった。
|
|
110
|
+
|
|
111
|
+
## [1.1.0] - 2026-04-13
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
- **`TemplateNotFoundError` / `DataNotFoundError` を構造化例外に変更** (`lib/query_stream.rb`, `lib/query_stream/errors.rb`):
|
|
116
|
+
従来は `render_query` 内で `logger.error` によるログ出力を行ってから例外を raise していた。
|
|
117
|
+
これでは呼び出し元がメッセージフォーマットや言語(i18n)を制御できないため、
|
|
118
|
+
gem 内のログ出力を廃止し、例外クラスに `template_path`, `query`, `location`, `hint`
|
|
119
|
+
(`DataNotFoundError` は `expected_path`, `query`, `location`)の属性を追加。
|
|
120
|
+
メッセージ構成・ログ出力・i18n はすべて呼び出し元の責務とした。
|
|
121
|
+
|
|
122
|
+
- **`render` 内で展開エラーをキャッチして後続の記法を継続展開するよう変更** (`lib/query_stream.rb`):
|
|
123
|
+
従来は1行の展開失敗で `render` 全体が中断されていた。
|
|
124
|
+
`render_query` の呼び出しを `rescue Error` で囲み、失敗した行は元の記法のまま残して
|
|
125
|
+
後続の QueryStream 記法の展開を継続するよう修正。
|
|
126
|
+
`on_error` コールバックを追加し、エラー情報を呼び出し元に通知できるようにした。
|
|
127
|
+
|
|
128
|
+
- **`Singularize` で `People`(大文字)が `person` に変換されない問題を修正** (`lib/query_stream/singularize.rb`):
|
|
129
|
+
`people` パターンの正規表現に `/i` フラグを追加し、大文字小文字を問わず変換できるようにした。
|
|
130
|
+
|
|
131
|
+
## [1.0.0] - 2026-03-21
|
|
132
|
+
|
|
133
|
+
### 🎉 最初のメジャーリリース
|
|
134
|
+
|
|
135
|
+
QueryStream 1.0.0 としてリリース!YAML/JSON データファイルとテンプレートファイルを組み合わせて、テキストコンテンツ内の QueryStream 記法を展開する汎用 Ruby ライブラリが完成しました。
|
|
136
|
+
|
|
137
|
+
### ✅ 実績と品質保証
|
|
138
|
+
- **vivlio-starter** プロジェクトで実稼働実績
|
|
139
|
+
- **109 テストケース**、**395 アサーション**で網羅的品質保証
|
|
140
|
+
- **VFM フェンス記法** 完全対応と実用性検証
|
|
141
|
+
|
|
142
|
+
### 🚀 主要機能
|
|
143
|
+
- QueryStream 記法の完全実装(源泉・抽出・ソート・件数・スタイル)
|
|
144
|
+
- VFM フェンス記法対応(`:::{.class-name}` 〜 `:::`)
|
|
145
|
+
- テンプレートコンパイラ(変数展開、画像記法、ネスト値アクセス)
|
|
146
|
+
- 高度なデータ処理(フィルタリング、ソート、単数形化)
|
|
147
|
+
- 柔軟な設定システム
|
|
148
|
+
|
|
149
|
+
### 📚 ドキュメント整備
|
|
150
|
+
- 詳細な README と具体例
|
|
151
|
+
- 完全な CHANGELOG
|
|
152
|
+
- Vivlio Starter での実装例
|
|
153
|
+
|
|
154
|
+
### 🔧 技術的特徴
|
|
155
|
+
- **Ruby 4.0+** モダン開発標準準拠
|
|
156
|
+
- **Semantic Versioning** 準拠
|
|
157
|
+
- 後方互換性保証
|
|
158
|
+
|
|
159
|
+
## [0.3.0] - 2026-03-21
|
|
160
|
+
|
|
161
|
+
### Added
|
|
162
|
+
- VFM フェンス記法対応 (`:::{.class-name}` 〜 `:::`)
|
|
163
|
+
- フェンス行が動的行を囲んでいる場合、フェンス行も repeating 範囲に含めて各レコードごとに反復出力
|
|
164
|
+
- `{.book-card}`, `{.person-card}` 等の任意 VFM クラス名に汎用的に対応
|
|
165
|
+
- フェンス記法関連のテストケースを追加 (12件)
|
|
166
|
+
- ファイルドキュメントに VFM フェンス記法対応の説明を追加
|
|
167
|
+
|
|
168
|
+
### Changed
|
|
169
|
+
- `TemplateCompiler` に `FENCE_OPEN_PATTERN` / `FENCE_CLOSE_PATTERN` 定数を追加
|
|
170
|
+
- `classify_lines` で `:fence_open` / `:fence_close` タイプを認識
|
|
171
|
+
- `expand_fence_range` メソッドを新設し、repeating 範囲の拡張ロジックを実装
|
|
172
|
+
|
|
173
|
+
### Fixed
|
|
174
|
+
- QueryStream と vivlio-starter の VFM フェンス記法の相性問題を解決
|
|
175
|
+
- フェンス行が一度しか出力されず、全レコードが1つのフェンスに押し込まれる問題を修正
|
|
176
|
+
|
|
177
|
+
## [0.2.0] - Previous Release
|
|
178
|
+
|
|
179
|
+
### Added
|
|
180
|
+
- 複数スタイル対応 (`:full`, `:table` 等)
|
|
181
|
+
- 高度なフィルタリング機能(範囲指定、不等値比較)
|
|
182
|
+
- パフォーマンス最適化
|
|
183
|
+
- エラーハンドリングの改善
|
|
184
|
+
|
|
185
|
+
## [0.1.0] - Initial Release
|
|
186
|
+
|
|
187
|
+
### Added
|
|
188
|
+
- 基本的な QueryStream 記法の実装
|
|
189
|
+
- 源泉指定 (`= books`)
|
|
190
|
+
- フィルタリング (`tags=ruby`, `condition=晴`)
|
|
191
|
+
- ソート (`-title`, `+date`)
|
|
192
|
+
- 件数制限 (`5`)
|
|
193
|
+
- スタイル指定 (`:full`)
|
|
194
|
+
- テンプレートコンパイラ機能
|
|
195
|
+
- 変数展開 (`= title`, `= author`)
|
|
196
|
+
- 画像記法の展開 (``)
|
|
197
|
+
- ネストされた値へのアクセス (`= author.name`)
|
|
198
|
+
- テーブル記法対応
|
|
199
|
+
- データフィルタリングとソート機能
|
|
200
|
+
- 等値フィルタ (`tags=ruby`)
|
|
201
|
+
- AND/OR 条件 (`tags=ruby && beginner`, `tags=ruby, javascript`)
|
|
202
|
+
- 比較演算子 (`>=`, `<=`, `>`, `<`, `!=`)
|
|
203
|
+
- データリゾルバー
|
|
204
|
+
- YAML/JSON ファイルの自動探索
|
|
205
|
+
- 複数形→単数形の自動テンプレート解決
|
|
206
|
+
- 単数形化ユーティリティ (Singularize)
|
|
207
|
+
- 英語の複数形パターン対応
|
|
208
|
+
- 不規則名詞 (people → person)
|
|
209
|
+
- 設定システム
|
|
210
|
+
- データディレクトリ、テンプレートディレクトリの設定
|
|
211
|
+
- ロガー設定
|
|
212
|
+
- エラー処理
|
|
213
|
+
- データファイル不在エラー
|
|
214
|
+
- テンプレートファイル不在エラー
|
|
215
|
+
- 不明キーエラー
|
data/README.md
CHANGED
|
@@ -13,6 +13,8 @@ gem 'query-stream'
|
|
|
13
13
|
bundle install
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
Ruby 3.4 以上が必要です。
|
|
17
|
+
|
|
16
18
|
## 基本的な使い方
|
|
17
19
|
|
|
18
20
|
```ruby
|
|
@@ -20,9 +22,11 @@ require 'query_stream'
|
|
|
20
22
|
|
|
21
23
|
# テキスト内の QueryStream 記法を展開
|
|
22
24
|
source = File.read('contents/05-references.md')
|
|
23
|
-
result = QueryStream.render(source)
|
|
25
|
+
result = QueryStream.render(source, data_dir: 'data', templates_dir: 'templates')
|
|
24
26
|
```
|
|
25
27
|
|
|
28
|
+
`data_dir` / `templates_dir` は後述の `configure` で既定値を決めておけば省略できます。
|
|
29
|
+
|
|
26
30
|
## QueryStream 記法
|
|
27
31
|
|
|
28
32
|
```
|
|
@@ -34,12 +38,118 @@ result = QueryStream.render(source)
|
|
|
34
38
|
```
|
|
35
39
|
= books # 全件展開
|
|
36
40
|
= books | tags=ruby # タグで絞り込み
|
|
41
|
+
= books | +title # タイトルの昇順
|
|
42
|
+
= books | -year # 発行年の降順
|
|
37
43
|
= books | tags=ruby | -title | 5 # 絞り込み+降順ソート+5件
|
|
44
|
+
= prefectures | population>=9_000_000 # 数は _ で桁を区切れる(900_0000 も可)
|
|
45
|
+
= weather_reports | date>=R06-01-01 # 日付は西暦(2024-01-01)でも和暦でも書ける
|
|
38
46
|
= book | 楽しいRuby # 主キーで一件検索
|
|
39
47
|
= books | :full # fullスタイルで展開
|
|
40
48
|
```
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
**ソートには符号を必ず付けてください**(`+` が昇順、`-` が降順)。符号を省いた
|
|
51
|
+
`= books | title` はソートではなく**主キー検索**として扱われ、該当なしの警告とともに
|
|
52
|
+
何も展開されません。`= book | 楽しいRuby` のような一件検索と綴りが同じになるため、
|
|
53
|
+
符号の有無で見分けています。
|
|
54
|
+
|
|
55
|
+
ソートでは、最も多い種類の値(数・日付・文字列)を比べます。値が無いレコードと、それ以外の
|
|
56
|
+
種類の値のレコードは、昇順でも降順でも末尾に置きます。
|
|
57
|
+
|
|
58
|
+
日付は、データでも抽出条件でも `2024-01-01`(西暦)か `R06-01-01`(和暦)で書きます。
|
|
59
|
+
和暦の元号は頭文字で書き(`M` 明治・`T` 大正・`S` 昭和・`H` 平成・`R` 令和)、年は 1 桁でも
|
|
60
|
+
構いません(`R6-01-01`)。どちらで書いても同じ日付として比べ、並べ替えます。
|
|
61
|
+
|
|
62
|
+
### 指定の順序
|
|
63
|
+
|
|
64
|
+
`|` で区切った指定は、省略しても、順序を入れ替えても構いません。形で種類を判別します。
|
|
65
|
+
|
|
66
|
+
| 形 | 種類 |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `:full`・`:card.html` | スタイル |
|
|
69
|
+
| `+title`・`-title` | ソート |
|
|
70
|
+
| `tags=ruby`・`year>=2020` のように演算子を含むもの | 抽出条件 |
|
|
71
|
+
| 正の整数 | 件数 |
|
|
72
|
+
| それ以外の値 | 主キー検索 |
|
|
73
|
+
|
|
74
|
+
単数形のデータ名に続く最初の指定は、数でも主キー検索として扱います(`= prefecture | 13`)。
|
|
75
|
+
|
|
76
|
+
### 抽出条件
|
|
77
|
+
|
|
78
|
+
| 書き方 | 意味 |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `tags=ruby`・`tags==ruby` | 等しい |
|
|
81
|
+
| `tags=ruby, go` | どちらかに等しい(OR) |
|
|
82
|
+
| `tags!=ruby` | 等しくない |
|
|
83
|
+
| `tags!=ruby, go` | どちらにも等しくない |
|
|
84
|
+
| `year>2020`・`year>=2020` | より大きい・以上 |
|
|
85
|
+
| `year<2020`・`year<=2020` | より小さい・以下 |
|
|
86
|
+
| `year=2020..2024` | 2020 以上 2024 以下 |
|
|
87
|
+
| `year=2020...2024` | 2020 以上 2024 未満 |
|
|
88
|
+
| `year=2020..` | 2020 以上 |
|
|
89
|
+
| `year=..2024` | 2024 以下 |
|
|
90
|
+
| `year=...2024` | 2024 未満 |
|
|
91
|
+
| `tags=ruby AND year>=2020` | 両方を満たす(`and`・`&&` も同じ) |
|
|
92
|
+
| `tags=ruby AND beginner` | `ruby` と `beginner` の両方を含む(2 件目以降の項目名は省ける) |
|
|
93
|
+
| `tags=ruby OR year>=2020` | どちらかを満たす(`or` も同じ) |
|
|
94
|
+
| `tags=ruby OR go` | どちらかを含む(2 件目以降の項目名は省ける) |
|
|
95
|
+
|
|
96
|
+
等しいかどうかを調べるとき、データの値が配列やカンマ区切りの文字列なら、要素のどれかが一致すれば条件を満たします。`tags: [ruby, web]` は `tags=ruby` に一致します。
|
|
97
|
+
|
|
98
|
+
AND は OR より強く結び付きます。`tags=ruby AND year>=2020 OR tags=go` は、「`ruby` で 2020 年以降」または「`go`」を選びます。
|
|
99
|
+
|
|
100
|
+
前後に空白のある `AND`・`and`・`OR`・`or`・`&&` は区切りとして扱うので、値には書けません。`||` は記法の区切りの `|` と重なるので使えません。
|
|
101
|
+
|
|
102
|
+
比較(`>`・`>=`・`<`・`<=`)と範囲では、値が無いレコードと、比較できない値のレコード(数と比べるときの `不明` など)を除きます。
|
|
103
|
+
|
|
104
|
+
### 一件検索
|
|
105
|
+
|
|
106
|
+
値だけを書くと、主キーで検索します。主キーとして調べるのは `id`・`no`・`code`・`slug`・`name`・`title` です。
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
= book | 楽しいRuby # title が一致するレコード
|
|
110
|
+
= post | hello-world # slug が一致するレコード
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
一致するレコードが無ければ `NoResultWarning` を通知し、何も展開しません。複数あれば `AmbiguousQueryWarning` を通知し、一致したレコードをすべて展開します。
|
|
114
|
+
|
|
115
|
+
### ファイルの探し方
|
|
116
|
+
|
|
117
|
+
データファイルは `data_dir` から次の順に探します。拡張子は `.yml`・`.yaml`・`.json` の順に試します。
|
|
118
|
+
|
|
119
|
+
1. 書いたままの名前(`books`)
|
|
120
|
+
2. 末尾に `s` を付けた名前(`book` → `books`)
|
|
121
|
+
3. 単数形にした名前(`books` → `book`)
|
|
122
|
+
|
|
123
|
+
テンプレートは `templates_dir` にある、単数形の名前のファイルを使います。
|
|
124
|
+
|
|
125
|
+
| 記法 | テンプレート |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `= books` | `_book.md` |
|
|
128
|
+
| `= books \| :full` | `_book.full.md` |
|
|
129
|
+
| `= books \| :card.html` | `_book.card.html` |
|
|
130
|
+
|
|
131
|
+
拡張子を書かないときの形式は、`config.default_format` で変更できます。
|
|
132
|
+
|
|
133
|
+
### テンプレートの書き方
|
|
134
|
+
|
|
135
|
+
| 書き方 | 意味 |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `= key`・`=key` | キーの値を展開する |
|
|
138
|
+
| `= author.name` | ネストした値を展開する |
|
|
139
|
+
| ``・`` | 画像のパスをキーの値で展開する |
|
|
140
|
+
| `` | 拡張子のあるパスはそのまま出力する |
|
|
141
|
+
|
|
142
|
+
変数を含む行はレコードごとに繰り返し、含まない行は 1 回だけ出力します。表は次のように書くと、見出しが 1 回だけ出力されます。
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
| 書名 | 著者 |
|
|
146
|
+
|---|---|
|
|
147
|
+
| = title | = author.name |
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
コードブロック(` ``` `)の中の記法は展開しません。
|
|
151
|
+
|
|
152
|
+
### 具体例:Vivlio Starter での書籍データ展開
|
|
43
153
|
|
|
44
154
|
#### データファイル例 (`data/books.yml`)
|
|
45
155
|
|
|
@@ -94,11 +204,55 @@ Rubyを楽しく学べる入門書。
|
|
|
94
204
|
C言語の定番入門書。
|
|
95
205
|
:::
|
|
96
206
|
|
|
207
|
+
|
|
97
208
|
---
|
|
98
209
|
```
|
|
99
210
|
|
|
100
211
|
VFMフェンス記法(`:::{.book-card}`)にも対応しており、各レコードが個別のフェンスで囲まれて展開されます。
|
|
101
212
|
|
|
213
|
+
### 値の無い項目
|
|
214
|
+
|
|
215
|
+
値が無い(キーが無い・空の)項目を含む行は、行ごと出力しません。項目によって情報量の
|
|
216
|
+
違うデータにも、同じテンプレートが使えます。
|
|
217
|
+
|
|
218
|
+
1 行に変数が 2 つ以上ある行では、**値の無い項目の部分だけ**を消します。行は区切り
|
|
219
|
+
(`/`・`・`・`、`・`,`・`,` と、前後に空白のある ` / `)と、変数を含む括弧(`(…)`・`(…)`)で
|
|
220
|
+
部品に分かれ、値の無い変数を含む部品だけが、区切りを詰めて消えます。変数に付いた言葉
|
|
221
|
+
(`著`・`年`)は、変数と一緒に残るか、一緒に消えます。
|
|
222
|
+
|
|
223
|
+
```markdown
|
|
224
|
+
= author.name 著/= publisher/= year 年
|
|
225
|
+
= author.name 著(= year年)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
| データ | 1 行目 | 2 行目 |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| 全部ある | 高橋征義 著/SBクリエイティブ/2019 年 | 高橋征義 著(2019年) |
|
|
231
|
+
| 発行年が無い | 高橋征義 著/SBクリエイティブ | 高橋征義 著 |
|
|
232
|
+
| 全部無い | (行ごと消える) | (行ごと消える) |
|
|
233
|
+
|
|
234
|
+
- 行頭の箇条書きの `- ` や見出しの `# ` は、先頭の部品が消えても残ります。
|
|
235
|
+
- 表の行(`|` で始まる行)は部品に分けず、値の無い項目があれば行ごと消えます(セルを消すと列がずれるため)。
|
|
236
|
+
- 空白なしの半角の `/` と、リンク先の括弧(`[= title](= url)`)は区切りになりません。
|
|
237
|
+
- テンプレートのキーは、どれか 1 件のデータにあれば正しいキーとみなします。どの件にも無いキーは打ち間違いとして `UnknownKeyError` になります。
|
|
238
|
+
|
|
239
|
+
## メソッド
|
|
240
|
+
|
|
241
|
+
| メソッド | 役割 |
|
|
242
|
+
|---|---|
|
|
243
|
+
| `QueryStream.render(text, **options)` | テキスト内の記法をすべて展開し、結果を返す |
|
|
244
|
+
| `QueryStream.render_query(query, **options)` | 1 つの記法を展開し、結果を返す |
|
|
245
|
+
| `QueryStream.scan(path_or_text)` | ファイルパスまたはテキストから記法を探し、一覧を返す |
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
QueryStream.render_query('= books | tags=ruby | :full', data_dir: 'data', templates_dir: 'templates')
|
|
249
|
+
|
|
250
|
+
QueryStream.scan('contents/05-references.md')
|
|
251
|
+
# => ["= books", "= books | :full"]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`render_query` は `on_error` を受け取りません。展開に失敗すると、例外をそのまま投げます。`scan` はコードブロックの中の記法を含めません。
|
|
255
|
+
|
|
102
256
|
## 設定
|
|
103
257
|
|
|
104
258
|
```ruby
|
|
@@ -106,10 +260,67 @@ QueryStream.configure do |config|
|
|
|
106
260
|
config.data_dir = 'data'
|
|
107
261
|
config.templates_dir = 'templates'
|
|
108
262
|
config.default_format = :md
|
|
109
|
-
config.
|
|
263
|
+
config.post_render = ->(text, context) { text }
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## エラー・警告の扱い
|
|
268
|
+
|
|
269
|
+
**この gem はログを出力しません。** どう表示するか、どの言語で出すか、ログに残すかは、呼び出し元が決めます。そのため展開時の出来事は、構造化された例外としてコールバックへ渡されます。例外の `message` には日本語の既定の文が入っています。
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
QueryStream.render(
|
|
273
|
+
content,
|
|
274
|
+
data_dir: 'data',
|
|
275
|
+
templates_dir: 'templates',
|
|
276
|
+
source_filename: 'chapter.md', # 位置情報(location)の表示に使います
|
|
277
|
+
on_error: ->(e) { }, # 展開失敗。その行は元の記法のまま残ります
|
|
278
|
+
on_warning: ->(w) { }, # 一件検索の該当なし/複数ヒット
|
|
279
|
+
post_render: ->(text, ctx) { } # 展開結果の後処理
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**1 行の失敗で全体は止まりません。** 失敗した行は元の記法のまま残り、後続の記法は展開され続けます。
|
|
284
|
+
|
|
285
|
+
| 例外クラス | 属性 |
|
|
286
|
+
|:---|:---|
|
|
287
|
+
| `TemplateNotFoundError` | `template_path`, `query`, `location`, `hint` |
|
|
288
|
+
| `DataNotFoundError` | `expected_path`, `query`, `location` |
|
|
289
|
+
| `DataLoadError` | `file_path`, `cause_error` |
|
|
290
|
+
| `UnknownKeyError` | `key_path`, `available_keys`, `template_path`, `location` |
|
|
291
|
+
| `InvalidDateError` | なし(`message` に日付を含みます) |
|
|
292
|
+
| `NoResultWarning` | `query`, `location` |
|
|
293
|
+
| `AmbiguousQueryWarning` | `query`, `location`, `count` |
|
|
294
|
+
|
|
295
|
+
前 5 つは `QueryStream::Error`、後 2 つは `QueryStream::Warning` を継承します。**gem の外へ出る例外はこの 2 系統だけ**です。壊れた YAML も、不正な UTF-8 を含む原稿も、生の `ArgumentError` や `NoMethodError` にはなりません。
|
|
296
|
+
|
|
297
|
+
たとえば `UnknownKeyError` は「使えるキーの一覧」と「直すべき雛形のパス」を属性に持つので、呼び出し元で打ち間違いの案内を組み立てられます。`location` は記法が書かれた原稿の位置、`template_path` は直す雛形の位置で、**直すのは雛形のほう**です。
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
on_error: lambda do |e|
|
|
301
|
+
next unless e.is_a?(QueryStream::UnknownKeyError)
|
|
302
|
+
|
|
303
|
+
keys = e.available_keys.map(&:to_s)
|
|
304
|
+
near = DidYouMean::SpellChecker.new(dictionary: keys).correct(e.key_path.split('.').first).first
|
|
305
|
+
puts "#{e.location} もしかして =#{near}?(#{e.template_path} / 使えるキー: #{keys.join(', ')})"
|
|
110
306
|
end
|
|
111
307
|
```
|
|
112
308
|
|
|
309
|
+
例外はファイル権限のみ扱いが異なり、`Errno::EACCES` のまま伝わります。環境の問題(`chmod` で直る)とデータの問題(中身を直す)は、対処が違うためです。
|
|
310
|
+
|
|
311
|
+
### `post_render`
|
|
312
|
+
|
|
313
|
+
展開結果を、その出どころの情報つきで受け取れます。gem 側は用途を規定しないので、画像パスの解決など固有の後処理を差し込めます。
|
|
314
|
+
|
|
315
|
+
```ruby
|
|
316
|
+
post_render: lambda do |text, context|
|
|
317
|
+
# context: source, data_file, data_dir, template_path, query, location
|
|
318
|
+
MyImageResolver.rewrite(text, context)
|
|
319
|
+
end
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
戻り値が `String` ならそれを、そうでなければ元の展開結果を採用します。
|
|
323
|
+
|
|
113
324
|
## ライセンス
|
|
114
325
|
|
|
115
326
|
MIT License
|
data/RELEASE_NOTE.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# QueryStream リリースノート
|
|
2
|
+
|
|
3
|
+
YAML / JSON のデータファイルとテンプレートを組み合わせて、文章中の 1 行記法を展開する Ruby ライブラリです。使い方は [README](README.md)、網羅的な変更履歴は [CHANGELOG](CHANGELOG.md) をご覧ください。
|
|
4
|
+
|
|
5
|
+
新しい版から順に並べています。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1.5.0 — 2026-11-01
|
|
10
|
+
|
|
11
|
+
**値の無い項目があっても、行ごと消さずに、その部分だけを消すようになりました。** 書誌のように一行へ複数の値を並べるテンプレートで、どれか一つが欠けると、これまでは行ごと消えていました。
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
= author.name 著/= publisher/= year 年
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
発行年の分からない本でも、`高橋征義 著/SBクリエイティブ` と出ます(1.4 では著者と出版社まで消えていました)。区切り(`/`・`・`・`、` など)と、変数を含む括弧(`(= year 年)`)で行を部品に分け、値の無い部品だけを、区切りを詰めて消します。すべての値がそろった行の出力は変わりません。変数が一つだけの行と表の行は、これまでどおり行ごと消します(表のセルを消すと列がずれるためです)。
|
|
18
|
+
|
|
19
|
+
**抽出条件の書き方を三つ足しました。**
|
|
20
|
+
|
|
21
|
+
- **`OR` で条件をつなげます。** `tags=ruby OR year>=2020` のように、別の項目の条件も書けます。AND は OR より強く結び付きます。前後に空白のある `OR` / `or` は区切りになるので、`title=Rock or Pop` のような値は抽出条件に書けなくなりました
|
|
22
|
+
- **数をアンダースコアで桁区切りして書けます。** `population>=9_000_000` や、万の位で区切る `900_0000` も書けます。比較・範囲・主キー検索・等値のどれでも使えます
|
|
23
|
+
- **日付を和暦(`R06-01-01`)でも書けます。** データでも抽出条件でも、西暦の `2024-01-01` と同じ日として比べ、並べ替えます。元号は頭文字(M・T・S・H・R)で書き、年は 1 桁でも構いません
|
|
24
|
+
|
|
25
|
+
**昇順の並べ替えの書き方を README に追加しました。** 機能は最初からありましたが、README の例は降順(`-title`)だけでした。
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
= books | +title # タイトルの昇順
|
|
29
|
+
= books | -year # 発行年の降順
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**符号は必ず付けてください。** 符号を省いた `= books | title` は、並べ替えではなく**一件検索**として解釈され、「該当なし」の警告を出して何も展開しません。`= book | 楽しいRuby` と同じ形なので、パーサーは符号の有無だけで両者を区別します。README にも理由を記載しました。
|
|
33
|
+
|
|
34
|
+
あわせて、内部に一度も実行されない分岐が残っていたのを整理しました。動きは変わりません。
|
|
35
|
+
|
|
36
|
+
- **対応 Ruby を 3.4.10 / 4.0.9 に更新しました**(4.0.9 は 2026 年 10 月 23 日公開)
|
|
37
|
+
- **Minitest を 6 系へ、samovar を 2.5.1 へ更新しました**
|
|
38
|
+
- **コード検査(rubocop)を導入しました。** 有効にしているのは `Lint` と `Security` の規則だけで、引用符の種類などの様式は検査しません
|
|
39
|
+
- README と CHANGELOG にあった「vivlio-style」という綴りを「Vivlio Starter」に直しました
|
|
40
|
+
|
|
41
|
+
### 不具合の修正
|
|
42
|
+
|
|
43
|
+
- **項目名を省いた AND が OR として動いていたのを直しました。** `tags=ruby AND beginner` は、`ruby` と `beginner` の両方を含むレコードを選ぶ記法ですが、どちらかを含むレコードを選んでいました。この書き方を使っている場合は、結果の件数が変わります
|
|
44
|
+
- **比較とソートで `ArgumentError` が発生していたのを直しました。** 対象の項目に値が無いレコードや比較できない値のレコードが 1 件でもあると、例外が `render` の外へ出ていました。比較では該当のレコードを除き、ソートでは昇順でも降順でも末尾に置きます。`year=2020..abc` のように両端を比較できない範囲も、例外にならなくなりました
|
|
45
|
+
- **省ける項目が最初の 1 件に無いと、`UnknownKeyError` が発生していたのを直しました。** テンプレートのキーを最初の 1 件だけで確認していました。修正後は、どれか 1 件にあるキーを正しいキーとして扱い、どの件にも無いキーだけを `UnknownKeyError` にします
|
|
46
|
+
- **日付の範囲指定が 1 件も返さなかったのを直しました。** `date=2024-06-01..2024-06-30` のような範囲は、両端が文字列のまま比較され、どのデータにも一致しませんでした。数の範囲は影響を受けていません
|
|
47
|
+
|
|
48
|
+
## 1.4.0 — 2026-08-07
|
|
49
|
+
|
|
50
|
+
**細かな不具合修正が中心のリリースです。** いずれも「著者が用意した原稿やデータが壊れている」という、実際に起こりうる場面で想定外の例外が漏れていた問題です。
|
|
51
|
+
|
|
52
|
+
- **空のデータファイルで落ちなくなりました。** 空 YAML は例外ではなく空配列として扱います。レコードでない中身(スカラーだけの YAML など)は、何が入っていたかを添えて `DataLoadError` で知らせます
|
|
53
|
+
- **壊れた文字が 1 バイト混ざっただけで展開が止まらなくなりました。** 読めない行は本文としてそのまま返します。gem が黙ってバイト列を書き換える(scrub する)設計は採っていません
|
|
54
|
+
- **`scan` に本文を渡しても落ちなくなりました。** パスと本文の両方を受け取る API なのに、引数を無条件でファイルとして開こうとしていました
|
|
55
|
+
- **`UnknownKeyError` が「使えるキーの一覧」と「直すべき雛形のパス」を持って届くようになりました。** これまでこの例外だけ属性が無く、キーの候補は gem が直接吐くログにしか出ていませんでした。`key_path` / `available_keys` / `template_path` / `location` を持たせ、**gem 内に最後まで残っていた `logger.error` を撤去**しています。呼び出し元は「もしかして `=title`?」のような案内を自前で組み立てられます
|
|
56
|
+
- **必要な Ruby を 4.0 以上から 3.4 以上へ緩めました**(コード変更はゼロ、gemspec 1 行のみ)
|
|
57
|
+
|
|
58
|
+
**破壊的変更が 1 つあります。** `QueryStream.logger` と `config.logger` を削除しました。上記の撤去で gem 内にロガーを読む箇所が無くなり、設定できるのに何も起きない項目になったためです。`config.logger = ...` と書いている箇所があれば、消してください(設定しても何も起きないので、消すだけで済みます)。`logger` gem への依存も外しました。
|
|
59
|
+
|
|
60
|
+
あわせて、これらを二度と起こさないための**堅牢性テスト 27 件**を `test/robustness/` に追加しました。守る約束は 1 つ、**gem の外へ出てよい例外は `QueryStream::Error` の系統だけ**です。呼び出し元は例外の型で扱いを分けるため、生の `ArgumentError` が出ると「どのファイルのどこが悪いか」を著者へ案内できません。上の 3 件は、このテストを書く過程で見つかりました。
|
|
61
|
+
|
|
62
|
+
ただし権限エラーだけは `Errno::EACCES` のまま伝えます。**環境の問題(`chmod` で直る)とデータの問題(中身を直す)は対処が違う**ためです。
|
|
63
|
+
|
|
64
|
+
## 1.3.0 — 2026-07-12
|
|
65
|
+
|
|
66
|
+
- **`post_render` 後段フィルタを追加しました。** 1 記法の展開結果を、コンテキスト付きで呼び出し元へ渡す汎用フックです。`context` には `source` / `data_file` / `data_dir` / `template_path` / `query` / `location` が入ります
|
|
67
|
+
- gem 自身は用途を規定しません。画像パス解決などの呼び出し元固有の後処理を、gem に固有知識を持ち込まずに差し込めます
|
|
68
|
+
|
|
69
|
+
## 1.2.2 — 2026-04-26
|
|
70
|
+
|
|
71
|
+
- **`physics_books` が `physic_books` になる単数形変換の誤りを修正しました。** アンダースコアで分割し、末尾から複数形セグメントを探して単数化します(`physics_books` → `physics_book`、`books_nested` → `book_nested`)
|
|
72
|
+
|
|
73
|
+
## 1.2.1 — 2026-04-20
|
|
74
|
+
|
|
75
|
+
- **データファイルの読み込みを `YAML.safe_load_file` へ移行しました(セキュリティ)。** `permitted_classes: [Symbol, Time, Date, DateTime]` を明示し、`!ruby/object:Kernel {}` のような Ruby オブジェクトタグを読ませて任意コード実行を狙う経路を塞いでいます。安全性が Psych のバージョンに依存しなくなりました
|
|
76
|
+
- **`DataLoadError` を追加しました。** `Psych::DisallowedClass` / `Psych::SyntaxError` / `JSON::ParserError` を、`file_path` と `cause_error` を持つ例外に変換します
|
|
77
|
+
|
|
78
|
+
## 1.2.0 — 2026-04-19
|
|
79
|
+
|
|
80
|
+
- **警告の `logger.warn` を全廃し、`on_warning:` コールバックで呼び出し元へ委譲しました。** `NoResultWarning` / `AmbiguousQueryWarning` に `query` / `location`(後者は `count` も)を持たせています
|
|
81
|
+
- これにより、`⚠️` の付け方も i18n も呼び出し元で決められるようになりました
|
|
82
|
+
|
|
83
|
+
> **補足**: このとき `TemplateCompiler` が `UnknownKeyError` を投げる経路にだけ `logger.error` が残っていました。1.4.0 で撤去し、**gem 内のログ出力はここで本当にゼロになりました**(設定項目 `config.logger` も同時に削除)。
|
|
84
|
+
|
|
85
|
+
## 1.1.0 — 2026-04-13
|
|
86
|
+
|
|
87
|
+
- **エラーを構造化例外へ移行しました。** `TemplateNotFoundError` に `template_path` / `query` / `location` / `hint`、`DataNotFoundError` に `expected_path` / `query` / `location` を持たせ、gem 内の `logger.error` を廃止しました
|
|
88
|
+
- **1 行の展開失敗で全体が止まる問題を修正しました。** 失敗した行は元の記法のまま残し、後続の展開を続けます。`on_error:` コールバックで通知します
|
|
89
|
+
- **`People`(大文字)が `person` に変換されない問題を修正しました**
|
|
90
|
+
|
|
91
|
+
## 1.0.0 — 2026-03-21
|
|
92
|
+
|
|
93
|
+
最初のメジャーリリースです。
|
|
94
|
+
|
|
95
|
+
- QueryStream 記法の完全実装(源泉・抽出条件・ソート・件数・スタイル)
|
|
96
|
+
- VFM フェンス記法対応(`:::{.book-card}` 〜 `:::`)。各レコードが個別のフェンスで囲まれて展開されます
|
|
97
|
+
- テンプレートコンパイラ(変数展開・画像記法・ネスト値アクセス・表記法)
|
|
98
|
+
- データリゾルバ(YAML / JSON の自動探索、複数形→単数形のテンプレート解決)
|
|
99
|
+
- フィルタリング(等値・AND / OR・比較演算子)とソート
|
|
100
|
+
- 設定システム(`data_dir` / `templates_dir` / `default_format` / `logger`)
|